package edu.njucm.herbiv.service

import edu.njucm.herbiv.config.ApplicationProperties
import edu.njucm.herbiv.dao.AnalysisOrderRepository
import edu.njucm.herbiv.dao.ConfigRepository
import edu.njucm.herbiv.model.*
import edu.njucm.herbiv.pojo.entity.AnalysisOrder
import edu.njucm.herbiv.pojo.entity.AnalysisStatus
import edu.njucm.herbiv.pojo.entity.FunctionType
import edu.njucm.herbiv.pojo.entity.PROCESS_TIMEOUT
import edu.njucm.herbiv.utils.FileUtils
import edu.njucm.herbiv.utils.ProcessUtils
import io.github.oshai.kotlinlogging.KotlinLogging
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import org.jetbrains.annotations.VisibleForTesting
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service
import java.io.IOException
import java.nio.file.Path
import java.sql.Timestamp
import java.util.*
import kotlin.io.path.Path
import kotlin.io.path.createDirectories
import kotlin.io.path.exists

private val logger = KotlinLogging.logger { }
/**
 * @Author ZhouHang
 * @Date 24/5/16 周四
 */
@Service
class HerbivService @Autowired constructor (
    private val analysisOrderRepository: AnalysisOrderRepository,
    private val configRepository: ConfigRepository,
    private val coroutineScope: CoroutineScope,
    applicationProperties: ApplicationProperties,
) {
    private val RESULTDIR = Path(applicationProperties.locPath)

    private fun getResultJsonPath(analysisNo: String): Path {
        return RESULTDIR.resolve(analysisNo).resolve(FileUtils.RESULTSNAME)
    }

    /**
     * 开启 python 进程执行分析
     * @param command 待执行的命令
     * @param analysisNo 分析号
     * */
    private fun from(command: List<String>, analysisNo: String, functionType: FunctionType) {
        RESULTDIR.resolve(analysisNo).createDirectories()
        val now = Timestamp(System.currentTimeMillis())
        var analysisOrder = AnalysisOrder(
            createTime = now,
            updateTime = now,
            command = command.joinToString(separator = " "),
            status = AnalysisStatus.ANALYSING,
            analysisNo = analysisNo,
            functionType = functionType
        )
        analysisOrder = analysisOrderRepository.save(analysisOrder)
        // 调用 herbiv 脚本执行分析
        try {
            val process: Process? = ProcessUtils.runCommand(command, true, getResultJsonPath(analysisNo))
            if (process == null) {
                logger.error { "[$analysisNo] 命令执行失败" }
                analysisOrder.status = AnalysisStatus.PROCESS_FAIL
                analysisOrderRepository.save(analysisOrder)
            } else {
                val pid = process.pid()
                // 启动定时器
                val timout = getProcessTimeout()
                val processTimoutTimer = Timer()
                processTimoutTimer.schedule(object : TimerTask() {
                    override fun run() {
                        // 检查 pid 是否还在
                        if (process.isAlive) {
                            process.destroyForcibly()
                            logger.info { "[$analysisNo]: 进程 $pid 运行超过十秒，自动关闭进程" }
                        } else {
                            cancel()
                        }
                    }
                }, timout, 10000)
                logger.info { "[$analysisNo] 正在等待进程 $pid 执行完毕, 当前超时配置：$timout ms" }
                process.waitFor()
                val exitValue = process.exitValue()
                if (exitValue == 0) {
                    processTimoutTimer.cancel()
                    logger.info { "[$analysisNo] 进程 $pid 正常退出" }
                    analysisOrder.status = AnalysisStatus.SUCCESS
                    analysisOrderRepository.save(analysisOrder)
                } else {
                    logger.info { "[$analysisNo] 进程 $pid 异常退出" }
                    analysisOrder.status = AnalysisStatus.PROCESS_FAIL
                    analysisOrderRepository.save(analysisOrder)
                }
            }
        } catch (e: IOException) {
            e.printStackTrace()
            analysisOrder.status = AnalysisStatus.CATCH_ERROR
            analysisOrderRepository.save(analysisOrder)
            return
        }
    }

    /**
     * 根据 TCM 编号分析
     * */
    fun fromTcm(req: FromTcmModel, analysisNo: String) {
        coroutineScope.launch {
            if (!existResult(analysisNo)) {
                val command = ProcessUtils.herbivCommand + listOf(
                    "--function", "tcm",
                    "--tcms") + req.tcms + listOf(
                    "--score", req.score.toString(),
                    "--path", RESULTDIR.resolve(analysisNo).toAbsolutePath().toString()
                )
                from(command, analysisNo, FunctionType.FROM_TCM)
            } else {
                logger.info { "$analysisNo: 已经分析过" }
            }
        }
    }

    /**
     * 根据 Formula 编号分析
     * */
    fun fromFormula(req: FromFormulaModel, analysisNo: String) {
        coroutineScope.launch {
            if (!existResult(analysisNo)) {
                val command = ProcessUtils.herbivCommand + listOf(
                    "--function", "formula",
                    "--formulas") + req.formulas + listOf(
                    "--score", req.score.toString(),
                    "--path", RESULTDIR.resolve(analysisNo).toAbsolutePath().toString()
                )
                from(command, analysisNo, FunctionType.FROM_FORMULA)
            } else {
                logger.info { "$analysisNo: 已经分析过" }
            }
        }
    }

    fun fromTcmProtein(req: FromTcmProteinModel, analysisNo: String) {
        coroutineScope.launch {
            if (!existResult(analysisNo)) {
                val command = ProcessUtils.herbivCommand + listOf(
                    "--function", "tcm_protein",
                    "--tcms") + req.tcms + listOf(
                    "--proteins") + req.proteins + listOf(
                    "--score", req.score.toString(),
                    "--path", RESULTDIR.resolve(analysisNo).toAbsolutePath().toString()
                )
                from(command, analysisNo, FunctionType.FROM_TCM_PROTEIN)
            } else {
                logger.info { "$analysisNo: 已经分析过" }
            }
        }
    }

    fun fromFormulaProtein(req: FromFormulaProtein, analysisNo: String) {
        coroutineScope.launch {
            if (!existResult(analysisNo)) {
                val command = ProcessUtils.herbivCommand + listOf(
                    "--function", "formula_protein",
                    "--formulas") + req.formulas + listOf(
                    "--proteins") + req.proteins + listOf(
                    "--score", req.score.toString(),
                    "--path", RESULTDIR.resolve(analysisNo).toAbsolutePath().toString()
                )
                from(command, analysisNo, FunctionType.FROM_FORMULA_PROTEIN)
            } else {
                logger.info { "$analysisNo: 已经分析过" }
            }
        }
    }

    fun fromProtein(req: FromProtein, analysisNo: String) {
        coroutineScope.launch {
            if (!existResult(analysisNo)) {
                val command = ProcessUtils.herbivCommand + listOf(
                    "--function", "protein",
                    "--proteins") + req.proteins + listOf(
                    "--score", req.score.toString(),
                    "--path", RESULTDIR.resolve(analysisNo).toAbsolutePath().toString()
                )
                from(command, analysisNo, FunctionType.FROM_PROTEIN)
            } else {
                logger.info { "$analysisNo: 已经分析过" }
            }
        }
    }

    /**
     * 若分析号已存在，则返回 True
     * @param analysisNo 分析号
     * */
    @VisibleForTesting
    internal fun existResult(analysisNo: String): Boolean {
        return getResult(analysisNo) != null
    }

    fun getResult(analysisNo: String): AnalysisOrder? {
        return analysisOrderRepository.findByAnalysisNo(analysisNo)
    }

    /**
     * @return 测试指定文件是否存在，存在返回 URI，不存在返回空字符串
     * */
    fun getFileUri(analysisNo: String, filename: String): String {
        return if (RESULTDIR.resolve(analysisNo).resolve(filename).exists()) {
            "$analysisNo/$filename"
        } else {
            ""
        }
    }

    /**
     * 返回指定功能在数据库中的条数
     * */
    fun getFunctionCount(function: FunctionType): Long {
        return analysisOrderRepository.countByFunctionType(function)
    }

    /**
     * 从 config 数据库中获取 processTimeout 值
     * @return 单位 ms
     * */
    private fun getProcessTimeout(): Long {
        val config = configRepository.findByKey(PROCESS_TIMEOUT) ?: return 10000
        return config.value.toLong() * 1000
    }
}
